package org.colin.aspect;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.colin.Tools.DateUtil;
import org.colin.Tools.TokenUtil;
import org.colin.Tools.Tool;
import org.colin.annotation.SystemControllerLog;
import org.colin.mapper.SysLogMapper;
import org.colin.model.ro.SysLogRO;
import org.colin.model.vo.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import com.alibaba.druid.support.json.JSONUtils;
import lombok.extern.slf4j.Slf4j;

/**
 * @desc 日志切面类
 * @author wujiangbo
 * @date 2020年1月2日 下午3:13:32
 */
@Aspect
@Component
@Slf4j
public class LogAspect {
	private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<Date>("ThreadLocal beginTime");

	private static final ThreadLocal<SysLogRO> logThreadLocal = new NamedThreadLocal<SysLogRO>("ThreadLocal log");

	private static final ThreadLocal<UserVO> currentUser = new NamedThreadLocal<>("ThreadLocal user");

	@Autowired(required = false)
	HttpServletRequest request;

	@Autowired
	ThreadPoolTaskExecutor threadPoolTaskExecutor;

	@Autowired
	private SysLogMapper sysLogMapper;

	@Autowired
	private TokenUtil tokenUtil;

	/**
	 * @desc Controller层切点 注解拦截
	 * @author wujiangbo
	 * @date 2020年1月2日 下午3:14:34
	 */
	@Pointcut("@annotation(org.colin.annotation.SystemControllerLog)")
	public void controllerAspect() {
	}

	/**
	 * @desc 前置通知 用于拦截Controller层记录用户的操作的开始时间
	 * @author wujiangbo
	 * @date 2020年1月2日 下午3:14:49
	 * @param joinPoint
	 * @throws InterruptedException
	 */
	@Before("controllerAspect()")
	public void doBefore(JoinPoint joinPoint) throws InterruptedException {
		log.debug("进入日志切面--前置通知");
		Date beginTime = new Date();
		beginTimeThreadLocal.set(beginTime);// 线程绑定变量（该数据只有当前请求的线程可见）
		// 这里日志级别为debug
		if (log.isDebugEnabled()) {
			log.debug("开始计时:{},URI:{}", new SimpleDateFormat(DateUtil.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS_SSS).format(beginTime), request.getRequestURI());
		}
		currentUser.set(tokenUtil.getRedisUser());
	}

	/**
	 * @desc 后置通知 用于拦截Controller层记录用户的操作
	 * @author wujiangbo
	 * @date 2020年1月2日 下午3:15:41
	 * @param joinPoint
	 */
	@After("controllerAspect()")
	public void doAfter(JoinPoint joinPoint) {
		log.debug("进入日志切面--后置通知");
		UserVO user = currentUser.get();
		if (user != null) {
			String title = "";
			String type = "info";// 日志类型(info:入库,error:错误)
			String remoteAddr = request.getRemoteAddr();// 请求的IP
			String requestUri = request.getRequestURI();// 请求的Uri
			String method = request.getMethod();// 请求的方法类型(post/get)
			try {
				title = getControllerMethodDescription(joinPoint);
			} catch (Exception e) {
				log.error("{}", e);
			}
			// 打印JVM信息。
			long beginTime = beginTimeThreadLocal.get().getTime();// 得到线程绑定的局部变量（开始时间）  
			long endTime = System.currentTimeMillis();// 2、结束时间  
			if (log.isDebugEnabled()) {
				log.debug("计时结束：{}  URI: {}  耗时： {}   最大内存: {}m  已分配内存: {}m  已分配内存中的剩余空间: {}m  最大可用内存: {}m", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(endTime), request.getRequestURI(), (endTime - beginTime), Runtime.getRuntime().maxMemory() / 1024 / 1024, Runtime.getRuntime().totalMemory() / 1024 / 1024, Runtime.getRuntime().freeMemory() / 1024 / 1024, (Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory() + Runtime.getRuntime().freeMemory()) / 1024 / 1024);
			}
			log.debug("设置日志信息到数据库中");
			SysLogRO log = new SysLogRO();
			log.setLogId(Tool.getPrimaryKey());
			log.setTitle(title);
			log.setType(type);
			log.setRemoteAddr(remoteAddr);
			log.setRequestUri(requestUri);
			log.setMethod(method);
			log.setException("无异常");
			log.setUserId(user.getId());
			Date operateDate = beginTimeThreadLocal.get();
			log.setOperateDate(operateDate);
			log.setTimeout(String.valueOf(endTime - beginTime));

			// 通过线程池来执行日志保存
			threadPoolTaskExecutor.execute(new SaveLogThread(log, sysLogMapper));
			logThreadLocal.set(log);
		}
	}

	/**
	 * @desc 处理完请求，返回内容
	 * @author wujiangbo
	 * @date 2020年1月2日 下午3:27:02
	 * @param res
	 * @throws Throwable
	 */
	@AfterReturning(returning = "res", pointcut = "controllerAspect()")
	public void doAfterReturning(Object res) throws Throwable {
		if (res != null) {
			String res_str = res.toString();
			String response_str = JSONUtils.toJSONString(res_str);
			log.debug("接口返回数据:" + response_str);
			SysLogRO logInfo = logThreadLocal.get();
			if (logInfo != null) {
				new UpdateLogThread(logInfo, sysLogMapper).start();
			}
		}
	}

	/**
	 * @desc 异常通知 记录操作报错日志
	 * @author wujiangbo
	 * @date 2020年1月2日 下午3:26:54
	 * @param joinPoint
	 * @param e
	 */
	@AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
		log.debug("进入日志切面异常通知!!");
		log.debug("异常信息:" + e.getMessage());
		String exception = e.toString();
		SysLogRO log = logThreadLocal.get();
		if (log != null) {
			log.setType("error");
			if (!Tool.isBlank(exception) && exception.length() > 255) {
				exception = exception.substring(0, 251);
			}
			log.setException(exception);
			new UpdateLogThread(log, sysLogMapper).start();
		}
	}

	/**
	 * @desc 保存日志线程
	 * @author wujiangbo
	 * @date 2020年1月2日 下午3:26:47
	 */
	private static class SaveLogThread implements Runnable {
		private SysLogRO log;

		private SysLogMapper sysLogMapper;

		public SaveLogThread(SysLogRO log, SysLogMapper sysLogMapper) {
			this.log = log;
			this.sysLogMapper = sysLogMapper;
		}

		@Override
		public void run() {
			log.setCreateTime(new Date());
			sysLogMapper.insert(log);
		}
	}

	/**
	 * @desc 日志更新线程
	 * @author wujiangbo
	 * @date 2020年1月2日 下午3:26:41
	 */
	private static class UpdateLogThread extends Thread {
		private SysLogRO log;

		private SysLogMapper sysLogMapper;

		public UpdateLogThread(SysLogRO log, SysLogMapper sysLogMapper) {
			super(UpdateLogThread.class.getSimpleName());
			this.log = log;
			this.sysLogMapper = sysLogMapper;
		}

		@Override
		public void run() {
			log.setCreateTime(new Date());
			this.sysLogMapper.update(log);
		}
	}

	/**
	 * @desc 获取注解中对方法的描述信息 用于Controller层注解
	 * @author wujiangbo
	 * @date 2020年1月2日 下午3:26:34
	 * @param joinPoint
	 * @return
	 */
	public static String getControllerMethodDescription(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method method = signature.getMethod();
		SystemControllerLog controllerLog = method.getAnnotation(SystemControllerLog.class);
		String discription = controllerLog.description();
		return discription;
	}
}
